package online.niuma.xiaocubao.service.impl;

import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import online.niuma.xiaocubao.common.Constants;
import online.niuma.xiaocubao.mapper.UserMapper;
import online.niuma.xiaocubao.pojo.entity.User;
import online.niuma.xiaocubao.pojo.vo.ErrorCode;
import online.niuma.xiaocubao.pojo.vo.LoginUserVo;
import online.niuma.xiaocubao.pojo.vo.Result;
import online.niuma.xiaocubao.pojo.vo.UpdateUserInfoParam;
import online.niuma.xiaocubao.service.IUserService;
import online.niuma.xiaocubao.utils.QiniuUtils;
import online.niuma.xiaocubao.utils.UserThreadLocal;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author 一颗蛋50斤
 * @since 2022-11-06
 */
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {

    @Value("${passwordSlat}")
    private String slat;

    @Resource
    private UserMapper userMapper;
    @Resource
    private RedisTemplate<String, String> redisTemplate;
    @Resource
    private QiniuUtils qiniuUtils;

    @Override
    public LoginUserVo queryUserInfoByAccountAndPassword(String account, String password) {
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        // 定义查询的条件 账号和密码
        queryWrapper.eq(User::getUserAccount, account);
        queryWrapper.eq(User::getUserPassword, password);
        // 定义查询内容
        queryWrapper.select(
                User::getUserId,
                User::getUserAccount,
                User::getUserNikename,
                User::getUserAvatar,
                User::getUserEmail,
                User::getUserToaccount,
                User::getUserAdmin,
                User::getUserStatus
        );
        User user = getOne(queryWrapper);
        // 如果用户不存在直接返回空对象
        if (user == null) { return null; }
        LoginUserVo loginUserVo = new LoginUserVo();
        // 将查询出来的属性拷贝
        BeanUtils.copyProperties(user, loginUserVo);
        return loginUserVo;
    }

    /**
     * 验证账号和邮箱
     * 从数据库中查询账号和邮箱
     * 如果在数据库中没有传递过来的账号和邮箱返回一个空值
     * 如果账号存在，返回账号已经存在的警告
     * 如果邮箱存在，返回邮箱已经存在的警告
     *
     * @param account 需要验证的账号
     * @param email   需要验证的邮箱
     * @return 验证的信息
     */
    @Override
    public Result checkAccountAndEmail(String account, String email) {
        // 根据用户的账号查询
        LambdaQueryWrapper<User> accountWrapper = new LambdaQueryWrapper<>();
        accountWrapper.eq(User::getUserAccount, account);
        User userAccount = getOne(accountWrapper);
        // 如果查询出来有结果，则表示账号存在
        if (userAccount != null) {
            return Result.fail(ErrorCode.ACCOUNT_UP_ERROR.getCode(), ErrorCode.ACCOUNT_UP_ERROR.getMsg());
        }
        // 根据邮箱查询
        LambdaQueryWrapper<User> emailWrapper = new LambdaQueryWrapper<>();
        emailWrapper.eq(User::getUserEmail, email);
        User userEmail = getOne(emailWrapper);
        // 如果查询出来有值，则表示邮箱已存在
        if (userEmail != null) {
            return Result.fail(ErrorCode.EMAIL_UP_ERROR.getCode(), ErrorCode.EMAIL_UP_ERROR.getMsg());
        }
        return null;
    }

    /**
     * 将用户的密码加密
     * 插入用户的信息
     *
     * @param account  账号
     * @param password 密码
     * @param email    邮箱
     * @return 是否插入成功
     */
    @Override
    public boolean insertUserInfo(String account, String password, String email) {
        // 对用户的密码进行加密
        password = DigestUtils.md5Hex(password + slat);
        // 创建 user 实例对象
        // 将账号、密码和邮箱加入实例对象属性中
        User user = new User();
        user.setUserAccount(account);
        user.setUserPassword(password);
        user.setUserEmail(email);
        int insert = userMapper.insert(user);
        return insert != 0;
    }

    /**
     * 修改用户的信息
     * 修改用户的昵称、邮箱和关联的账号
     *
     * @param updateUserInfoParam 用户所需要修改的信息
     * @return 修改结果
     */
    @Override
    public Result updateUserInfo(String token,UpdateUserInfoParam updateUserInfoParam) {
        // 获取到用户登陆的信息
        LoginUserVo loginUserVo = UserThreadLocal.get();
        // 获取到用户的id
        Long userId = loginUserVo.getUserId();
        // 需要修改的昵称
        String userNikename = updateUserInfoParam.getUserNikename();
        // 需要修改的用户邮箱
        String userEmail = updateUserInfoParam.getUserEmail();
        // 需要修改的关联邮箱
        String userToaccount = updateUserInfoParam.getUserToaccount();

        // 穿件用户实例对象
        User user = new User();
        // 将需要更改的数据插入对象中
        user.setUserId(userId);
        user.setUserNikename(userNikename);
        user.setUserEmail(userEmail);
        user.setUserToaccount(userToaccount);
        // 对用户的信息进行更改
        int updateRowNumber = userMapper.updateById(user);
        // 如果数据更改的数据行数为零则表示修改失败
        if (updateRowNumber == 0) {
            // 返回修改失败的信息
            return Result.fail(ErrorCode.UPDATE_USERINFO_ERROR.getCode(), ErrorCode.UPDATE_USERINFO_ERROR.getMsg());
        }

        // 更新登陆用户的信息
        loginUserVo.setUserNikename(userNikename);
        loginUserVo.setUserEmail(userEmail);
        loginUserVo.setUserToaccount(userToaccount);
        // 更新 redis 中的值
        updateTokenValue(loginUserVo, token);
        return Result.success("用户信息修改成功");
    }

    /**
     * 修改用户的密码
     *
     * @param oldPassword 原始密码
     * @param newPassword 新的密码
     * todo 验证密码的真实性，验证所有修改密码的合法性，对密码进行修改
     * @return 修改的信息
     */
    @Override
    public Result updateUserPassword(String oldPassword, String newPassword) {
        // 获取到新密码的长度
        int newPasswordLength = newPassword.length();
        // 定义密码的最小长度和最大长度
        int minLength = 6, maxLength = 16;
        // 判定新密码的合法性
        if (newPasswordLength < minLength || newPasswordLength > maxLength) {
            // 如果新的密码不符合规则，返回密码不符合规则的错误信息
            return Result.fail(ErrorCode.PASSWORD_LENGTH_ERROR.getCode(), ErrorCode.PASSWORD_LENGTH_ERROR.getMsg());
        }

        // 获取到登陆用户的id
        Long userId = UserThreadLocal.get().getUserId();
        // 将旧密码进行加密
        oldPassword = DigestUtils.md5Hex(oldPassword + slat);

        // 创建查询条件
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(User::getUserId, userId);
        queryWrapper.eq(User::getUserPassword, oldPassword);
        queryWrapper.select(User::getUserId, User::getUserPassword);
        User user = userMapper.selectOne(queryWrapper);
        // 如果查询出来的用户信息为空则表示密码匹配
        // 返回密码错误的消息
        if (user == null) {
            return Result.fail(ErrorCode.OLD_PASSWORD_ERROR.getCode(), ErrorCode.OLD_PASSWORD_ERROR.getMsg());
        }

        // 对旧密码进行更新
        newPassword = DigestUtils.md5Hex(newPassword + slat);
        user.setUserPassword(newPassword);
        int updateRowNumber = userMapper.updateById(user);

        return updateRowNumber != 0 ?
                Result.success("密码修改成功") :
                Result.fail(320, "密码修改失败");
    }

    /**
     * 验证两个账号是否是相互关联的
     * 先从关联的账号中查询它所关联的账号
     * 然后将用户的账号和用户关联的账号中所查询到的关联账号进行对比
     * 也就是说看对方关联的账号是不是自己的这个账号
     * 如果对方关联的账号也是自己的账号，那么这两个账号是互相关联的
     * @param account     用户的账号
     * @param linkAccount 关联的账号
     * @return 如果两个账号是关联的返回对方账号的邮箱，否则返回空
     */
    @Override
    public String checkMeRelationYou(String account, String linkAccount) {
        // 创建查询条件构造器
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        // 将关联的账号作为查询条件
        queryWrapper.eq(User::getUserAccount, linkAccount);
        // 查询对方关联的账号和邮箱
        queryWrapper.select(User::getUserToaccount, User::getUserEmail);
        User user = userMapper.selectOne(queryWrapper);
        // 如果结果为空则表示没有这个账号
        if (user == null) {
            return null;
        }
        // 获取到对方账号关联的账号
        String userToaccount = user.getUserToaccount();
        // 如果两个账号相等则表示
        if (account.equals(userToaccount)) {
            // 放回对方账号的邮箱
            return user.getUserEmail();
        }
        return null;
    }

    /**
     * 修改用户的头像
     * 将头像上传到七牛云
     * 如果上传成功将数据库中的用户头像信息进行修改
     * 将 Redis 缓存中的缓存进行修改
     * 将原有的头像进行删除
     * @param file     头像文件
     * @param fileName 文件名称
     * @param token user token
     * @return 修改头像的信息,如果头像修改成功返回头像的地址，如果修改失败则返回错误的信息
     */
    @Override
    public Result updateUserAvatar(MultipartFile file, String fileName, String token) {
        String uploadInfo = qiniuUtils.upload(file, fileName);
        String uploadError = "上传失败";
        if (uploadError.equals(uploadInfo)) {
            return Result.fail(-1, uploadError);
        }
        // 获取登陆用户的 id
        LoginUserVo loginUserVo = UserThreadLocal.get();
        String oldAvatarUrl = loginUserVo.getUserAvatar();
        User user = new User();
        user.setUserId(loginUserVo.getUserId());
        user.setUserAvatar(uploadInfo);
        // 更新数据库
        userMapper.updateById(user);
        loginUserVo.setUserAvatar(uploadInfo);
        // 更新 redis value
        updateTokenValue(loginUserVo, token);
        // 删除原有的头像
        qiniuUtils.delete(oldAvatarUrl);
        return Result.success(uploadInfo);
    }

    /**
     * 修改 redis 中 token 的 value
     * @param loginUserVo 登陆用户的信息
     * @param token 需要修改的 token
     */
    private void updateTokenValue(LoginUserVo loginUserVo, String token) {
        // 更新 redis 中的值
        // 从 redis 中查询 token
        String loginToken = redisTemplate.opsForValue().get(Constants.TOKEN + token);
        // 如果在 redis 中存在则将其删除
        if (!StringUtils.isBlank(loginToken)) {
            redisTemplate.delete(Constants.TOKEN + token);
        }
        // 将新的值存入 redis 中
        // 也许你会问为什么不直接更新 redis 中的 value 值
        // 这个我试过，如果直接 set 的话设置的时效就会失效
        redisTemplate
                .opsForValue()
                .set(Constants.TOKEN + token, JSON.toJSONString(loginUserVo), 5, TimeUnit.HOURS);
    }

}
